#include <fstream>
#include <sstream>
#include <algorithm>
#include <stdexcept>
#include <vector>
#include <deque>
#include <string>
#include <iostream>


#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/foreach.hpp>



#define DEBUG_LEVEL 6
#include <debug_func.h>

#include <seq_pairs.h>
#include <get_filename.h>
#include <index_string.h>
#include <portable_timer.h>
#include <progress_indicator.h>
#include <stlhelper.h>
#include <ultoa.h>
#include <print_error.h>
#include <open_fstream.h>
#include <open_stream_name_with_substitutions.h>
#include <crc.h>
#include <digest.h>
#include "get_arg.h"
#include <boost/algorithm/string.hpp>


using std::vector;
using std::deque;
using std::ofstream;
using std::ifstream;
using std::istream;
using std::istringstream;
using std::ostream;
using std::string;
using std::runtime_error;
using std::cerr;
using std::cin;
using std::cout;

//________________________________________________________________________________________

//		get_ids_from_file

//________________________________________________________________________________________
void get_clades_from_file(	const string&			file_path, 
							t_index_string&			identifiers, 
							std::deque<unsigned>&	identifier_to_clade_id,
							t_program_args&			args)
{
	string file_name = get_lastdir_filename(file_path);
	args.VERBOSE() << "\tParsing clades from [" << file_name << "]...\n";

	ifstream ifs;
	open_fstream(ifs, file_path);
	unsigned DOTS_SIZE = 100000;
	t_progress_indicator dots(args.VERBOSE(2),  DOTS_SIZE);
	dots.use_timer();
	unsigned cnt_lines = 0;

	t_index_string clade_names;
	
	string line;
	while (getline(ifs, line))
	{
		++cnt_lines;

		// ignore comment lines
		if (args.comment_letters.find(line[0]) != std::string::npos)
			continue;

		// split into identifier and name
		std::vector<std::string> fields;
		boost::split(fields, line, std::bind2nd(std::equal_to<char>(), '\t'));
		if (fields.size() != 2)
			throw std::runtime_error("Line # " + ultoa(cnt_lines) + 
									 " from [" + file_name + "] was not in the "
															   "correct format...\n");
		unsigned clade_index = clade_names.index(fields[1]);

		// add identifier to clade lookup unless previously added
		if (identifiers.index(fields[0]) == identifier_to_clade_id.size())
		{
			identifier_to_clade_id.push_back(clade_index);
		}

		++dots;
	}
	dots.finish();
	if (!ifs.eof())
		throw std::runtime_error("Could not read from [" + file_name + "]...\n");
	if (cnt_lines != identifiers.size())
	{
		args.VERBOSE() << "\t\t" << identifiers.size()
						<< " unique identifiers out of " << cnt_lines
						<< " read from [" << file_name << "].\n";
		args.SUMMARY()	<< identifiers.size()
						<< " unique identifiers out of\n"
						<< cnt_lines
						<< " read from [" << file_name << "].\n";
	}				
	else
	{
		args.VERBOSE(2)	<< "\t\t" << cnt_lines
						<< " identifiers read from [" << file_name << "].\n";
		args.SUMMARY()	<< cnt_lines
						<< " identifiers read from [" << file_name << "].\n";
	}				
}



//________________________________________________________________________________________

//		do_parse_pairs

//________________________________________________________________________________________
struct do_parse_pairs
{
private:
	std::string						comment_letters;
public:
	const t_index_string&			identifiers;
	t_index_string					cladeless_identifiers;
	deque<unsigned>&				identifier_to_clade_id;
	t_program_args&					args;
	unsigned						cnt_all_lines;
	unsigned						cnt_printed;
	
	do_parse_pairs(	const t_index_string&				identifiers_,
					deque<unsigned>&					identifier_to_clade_id_,
					t_program_args&						args_,
					const std::string&					comment_letters_):
			comment_letters(comment_letters_),
			identifiers(identifiers_), 
			identifier_to_clade_id(identifier_to_clade_id_),
			args(args_),
			cnt_printed(0)
			{
			}
	void operator()(istream& is, unsigned cnt_line)
	{
		// get data for one protein pair
		string name1, name2;

		// comment if false
		string comment_line;
		if (!seq_pairs::parse_ids(is, name1, name2, comment_line, comment_letters,
								  seq_pairs::ePARSE_IDS_ONLY))
		{
			return;
		}		

		cnt_all_lines = cnt_line;

		string line;
		if (is.peek() == '\t')
		{
			getline(is, line);
			if (!is)
				throw std::runtime_error("The data is not in the correct format");
			// will fail if this is the end of file for either the initial line or the whole
			// 	thing
			is.putback('\n');
		}

		line = name1 + '\t' + name2 + line;

		// make sure the identifiers belong to the same clade
		unsigned id1, id2;
		if (!identifiers.is_indexed(name1, id1))
		{
			cladeless_identifiers.index(name1);
			return;
		}
		if (!identifiers.is_indexed(name2, id2))
		{
			cladeless_identifiers.index(name2);
			return;
		}
		assert (id1 < identifier_to_clade_id.size());
		assert (id2 < identifier_to_clade_id.size());
		if (identifier_to_clade_id[id1]  != identifier_to_clade_id[id2])
			return;

		cout << line << "\n";
		++cnt_printed;
	}
};



//________________________________________________________________________________________

//		main

//________________________________________________________________________________________
int main(int argc, char* argv[])
{
	t_program_args args;

	try
	{

	debug_func_cerr(5);

	// don't use C streams
	std::ios::sync_with_stdio(false);


	// get command line arguments
	if (!process_command_line_options(argc, argv, args))
		return 1;


	//
	//	retrieve identifiers
	//
	t_index_string			identifiers;
	std::deque<unsigned>	identifier_to_clade_id;
	get_clades_from_file(args.file_clades, identifiers, identifier_to_clade_id, args);

	//
	//	print matching
	//
	string file_name = args.file_input.length() ? 
						 get_filename(args.file_input) : 
						 "STDIN";
	args.VERBOSE() << "\tReading sequence pair data from [" + file_name + "]\n";
	do_parse_pairs parse_pairs(identifiers, identifier_to_clade_id, args,
								args.comment_letters);

	seq_pairs::read_data(parse_pairs, args.file_input, args.VERBOSE());
	args.VERBOSE(1) << "\t\t" << parse_pairs.cnt_all_lines
					<< " lines read from [" << file_name << "].\n";
	args.SUMMARY()	<< parse_pairs.cnt_all_lines
					<< " lines read from [" << file_name << "].\n";
	args.VERBOSE(1) << "\t\t" << parse_pairs.cnt_printed
					<< " lines with both identifiers in the same clade.\n";
	args.SUMMARY()	<< "\t\t" << parse_pairs.cnt_printed
					<< " lines with both identifiers in the same clade.\n";
	
	args.VERBOSE() << "\tAll finished.\n";

	}
	catch (std::exception& e)
	{
		std_print_error(args.CERR(), e.what());
		return 1;
	}

	return 0;
}

